{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "V4zfNSMd0zwZ"
   },
   "source": [
    "##### Copyright 2020 The OpenFermion Developers"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "cellView": "form",
    "id": "lgoZIZsn02PV"
   },
   "outputs": [],
   "source": [
    "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
    "# you may not use this file except in compliance with the License.\n",
    "# You may obtain a copy of the License at\n",
    "#\n",
    "# https://www.apache.org/licenses/LICENSE-2.0\n",
    "#\n",
    "# Unless required by applicable law or agreed to in writing, software\n",
    "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
    "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
    "# See the License for the specific language governing permissions and\n",
    "# limitations under the License."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "cIiW50gV07qV"
   },
   "source": [
    "# The Jordan-Wigner and Bravyi-Kitaev Transforms"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "dsmK__RC08h-"
   },
   "source": [
    "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
    "  <td>\n",
    "    <a target=\"_blank\" href=\"https://quantumai.google/openfermion/tutorials/jordan_wigner_and_bravyi_kitaev_transforms\"><img src=\"https://quantumai.google/site-assets/images/buttons/quantumai_logo_1x.png\" />View on QuantumAI</a>\n",
    "  </td>\n",
    "  <td>\n",
    "    <a target=\"_blank\" href=\"https://colab.research.google.com/github/quantumlib/OpenFermion/blob/master/docs/tutorials/jordan_wigner_and_bravyi_kitaev_transforms.ipynb\"><img src=\"https://quantumai.google/site-assets/images/buttons/colab_logo_1x.png\" />Run in Google Colab</a>\n",
    "  </td>\n",
    "  <td>\n",
    "    <a target=\"_blank\" href=\"https://github.com/quantumlib/OpenFermion/blob/master/docs/tutorials/jordan_wigner_and_bravyi_kitaev_transforms.ipynb\"><img src=\"https://quantumai.google/site-assets/images/buttons/github_logo_1x.png\" />View source on GitHub</a>\n",
    "  </td>\n",
    "  <td>\n",
    "    <a href=\"https://storage.googleapis.com/tensorflow_docs/OpenFermion/docs/tutorials/jordan_wigner_and_bravyi_kitaev_transforms.ipynb\"><img src=\"https://quantumai.google/site-assets/images/buttons/download_icon_1x.png\" />Download notebook</a>\n",
    "  </td>\n",
    "</table>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "klChkhbu1GtK"
   },
   "source": [
    "## Setup\n",
    "\n",
    "Install the OpenFermion package:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "5cbe6b680387"
   },
   "outputs": [],
   "source": [
    "try:\n",
    "    import openfermion\n",
    "except ImportError:\n",
    "    !pip install git+https://github.com/quantumlib/OpenFermion.git@master#egg=openfermion"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "87a7d8066173"
   },
   "source": [
    "## Ladder operators and the canonical anticommutation relations\n",
    "\n",
    "A system of $N$ fermionic modes is\n",
    "described by a set of fermionic *annihilation operators*\n",
    "$\\{a_p\\}_{p=0}^{N-1}$ satisfying the *canonical anticommutation relations*\n",
    "$$\\begin{align}\n",
    "    \\{a_p, a_q\\} &= 0, \\label{eq:car1} \\\\\n",
    "    \\{a_p, a^\\dagger_q\\} &= \\delta_{pq}, \\label{eq:car2}\n",
    "  \\end{align}$$ where $\\{A, B\\} := AB + BA$. The adjoint\n",
    "$a^\\dagger_p$ of an annihilation operator $a_p$ is called a *creation\n",
    "operator*, and we refer to creation and annihilation operators as\n",
    "fermionic *ladder operators*.\n",
    "In a finite-dimensional vector space the anticommutation relations have the following consequences:\n",
    "\n",
    "-   The operators $\\{a^\\dagger_p a_p\\}_{p=0}^{N-1}$ commute with each\n",
    "    other and have eigenvalues 0 and 1. These are called the *occupation\n",
    "    number operators*.\n",
    "\n",
    "-   There is a normalized vector $\\lvert{\\text{vac}}\\rangle$, called the *vacuum\n",
    "    state*, which is a mutual 0-eigenvector of all\n",
    "    the $a^\\dagger_p a_p$.\n",
    "\n",
    "-   If $\\lvert{\\psi}\\rangle$ is a 0-eigenvector of $a_p^\\dagger a_p$, then\n",
    "    $a_p^\\dagger\\lvert{\\psi}\\rangle$ is a 1-eigenvector of $a_p^\\dagger a_p$.\n",
    "    This explains why we say that $a_p^\\dagger$ creates a fermion in\n",
    "    mode $p$.\n",
    "\n",
    "-   If $\\lvert{\\psi}\\rangle$ is a 1-eigenvector of $a_p^\\dagger a_p$, then\n",
    "    $a_p\\lvert{\\psi}\\rangle$ is a 0-eigenvector of $a_p^\\dagger a_p$. This\n",
    "    explains why we say that $a_p$ annihilates a fermion in mode $p$.\n",
    "\n",
    "-   $a_p^2 = 0$ for all $p$. One cannot create or annihilate a fermion\n",
    "    in the same mode twice.\n",
    "    \n",
    "-   The set of $2^N$ vectors\n",
    "    $$\\lvert n_0, \\ldots, n_{N-1} \\rangle :=\n",
    "      (a^\\dagger_0)^{n_0} \\cdots (a^\\dagger_{N-1})^{n_{N-1}} \\lvert{\\text{vac}}\\rangle,\n",
    "      \\qquad n_0, \\ldots, n_{N-1} \\in \\{0, 1\\}$$\n",
    "    are orthonormal. We can assume they form a basis for the entire vector space.\n",
    "    \n",
    "-   The annihilation operators $a_p$ act on this basis as follows:\n",
    "    $$\\begin{aligned} a_p \\lvert n_0, \\ldots, n_{p-1}, 1, n_{p+1}, \\ldots, n_{N-1} \\rangle &= (-1)^{\\sum_{q=0}^{p-1} n_q} \\lvert n_0, \\ldots, n_{p-1}, 0, n_{p+1}, \\ldots, n_{N-1} \\rangle \\,, \\\\ a_p \\lvert n_0, \\ldots, n_{p-1}, 0, n_{p+1}, \\ldots, n_{N-1} \\rangle &= 0 \\,.\\end{aligned}$$\n",
    "    \n",
    "See [here](http://michaelnielsen.org/blog/archive/notes/fermions_and_jordan_wigner.pdf) for a derivation and discussion of these\n",
    "consequences.\n",
    "\n",
    "## Mapping fermions to qubits with transforms\n",
    "\n",
    "To simulate a system of fermions on a quantum computer, we must choose a representation of the ladder operators on the Hilbert space of the qubits. In other words, we must designate a set of qubit operators (matrices) which satisfy the canonical anticommutation relations. Qubit operators are written in terms of the Pauli matrices $X$, $Y$, and $Z$. In OpenFermion a representation is specified by a transform function which maps fermionic operators (typically instances of FermionOperator) to qubit operators (instances of QubitOperator). In this demo we will discuss the Jordan-Wigner and Bravyi-Kitaev transforms, which are implemented by the functions `jordan_wigner` and `bravyi_kitaev`.\n",
    "\n",
    "### The Jordan-Wigner Transform\n",
    "Under the Jordan-Wigner Transform (JWT), the annihilation operators are mapped to qubit operators as follows:\n",
    "$$\\begin{aligned}\n",
    "    a_p &\\mapsto \\frac{1}{2} (X_p + \\mathrm{i}Y_p) Z_1 \\cdots Z_{p - 1} \\\\\n",
    "    &= (\\lvert{0}\\rangle\\langle{1}\\rvert)_p Z_1 \\cdots Z_{p - 1} \\\\\n",
    "    &=: \\tilde{a}_p.\n",
    "\\end{aligned}$$\n",
    "\n",
    "This operator has the following action on a computational basis vector\n",
    "$\\lvert z_0, \\ldots, z_{N-1} \\rangle$:\n",
    "$$\\begin{aligned}\n",
    "    \\tilde{a}_p \\lvert z_0 \\ldots, z_{p-1}, 1, z_{p+1}, \\ldots, z_{N-1} \\rangle &=\n",
    "    (-1)^{\\sum_{q=0}^{p-1} z_q} \\lvert z_0 \\ldots, z_{p-1}, 0, z_{p+1}, \\ldots, z_{N-1} \\rangle \\\\\n",
    "    \\tilde{a}_p \\lvert z_0 \\ldots, z_{p-1}, 0, z_{p+1}, \\ldots, z_{N-1} \\rangle &= 0.\n",
    "  \\end{aligned}$$\n",
    "\n",
    "Note that $\\lvert n_0, \\ldots, n_{N-1} \\rangle$ is a basis vector in the Hilbert space of fermions, while $\\lvert z_0, \\ldots, z_{N-1} \\rangle$ is a basis vector in the Hilbert space of qubits. Similarly, in OpenFermion $a_p$ is a FermionOperator while $\\tilde{a}_p$ is a QubitOperator.\n",
    "\n",
    "Let's instantiate some FermionOperators, map them to QubitOperators using the JWT, and check that the resulting operators satisfy the expected relations."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "acafe41c2734"
   },
   "outputs": [],
   "source": [
    "from openfermion import *\n",
    "\n",
    "# Create some ladder operators\n",
    "annihilate_2 = FermionOperator('2')\n",
    "create_2 = FermionOperator('2^')\n",
    "annihilate_5 = FermionOperator('5')\n",
    "create_5 = FermionOperator('5^')\n",
    "\n",
    "# Construct occupation number operators\n",
    "num_2 = create_2 * annihilate_2\n",
    "num_5 = create_5 * annihilate_5\n",
    "\n",
    "# Map FermionOperators to QubitOperators using the JWT\n",
    "annihilate_2_jw = jordan_wigner(annihilate_2)\n",
    "create_2_jw = jordan_wigner(create_2)\n",
    "annihilate_5_jw = jordan_wigner(annihilate_5)\n",
    "create_5_jw = jordan_wigner(create_5)\n",
    "num_2_jw = jordan_wigner(num_2)\n",
    "num_5_jw = jordan_wigner(num_5)\n",
    "\n",
    "# Create QubitOperator versions of zero and identity\n",
    "zero = QubitOperator()\n",
    "identity = QubitOperator(())\n",
    "\n",
    "# Check the canonical anticommutation relations\n",
    "assert anticommutator(annihilate_5_jw, annihilate_2_jw) == zero\n",
    "assert anticommutator(annihilate_5_jw, annihilate_5_jw) == zero\n",
    "assert anticommutator(annihilate_5_jw, create_2_jw) == zero\n",
    "assert anticommutator(annihilate_5_jw, create_5_jw) == identity\n",
    "\n",
    "# Check that the occupation number operators commute\n",
    "assert commutator(num_2_jw, num_5_jw) == zero\n",
    "\n",
    "# Print some output\n",
    "print(\"annihilate_2_jw = \\n{}\".format(annihilate_2_jw))\n",
    "print('')\n",
    "print(\"create_2_jw = \\n{}\".format(create_2_jw))\n",
    "print('')\n",
    "print(\"annihilate_5_jw = \\n{}\".format(annihilate_5_jw))\n",
    "print('')\n",
    "print(\"create_5_jw = \\n{}\".format(create_5_jw))\n",
    "print('')\n",
    "print(\"num_2_jw = \\n{}\".format(num_2_jw))\n",
    "print('')\n",
    "print(\"num_5_jw = \\n{}\".format(num_5_jw))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "2281a1a8dafd"
   },
   "source": [
    "### The parity transform\n",
    "\n",
    "By comparing the action of $\\tilde{a}_p$ on $\\lvert z_0, \\ldots, z_{N-1} \\rangle$ in the JWT with the action of $a_p$ on $\\lvert n_0, \\ldots, n_{N-1} \\rangle$ (described in the first section of this demo), we can see that the JWT is associated with a particular mapping of bitstrings $e: \\{0, 1\\}^N \\to \\{0, 1\\}^N$, namely, the identity map $e(x) = x$. In other words, under the JWT, the fermionic basis vector $\\lvert n_0, \\ldots, n_{N-1} \\rangle$ is represented by the computational basis vector $\\lvert z_0, \\ldots, z_{N-1} \\rangle$, where $z_p = n_p$ for all $p$. We can write this as\n",
    "$$\\lvert x \\rangle \\mapsto \\lvert e(x) \\rangle,$$\n",
    "where the vector on the left is fermionic and the vector on the right is qubit. We call the mapping $e$ an *encoder*.\n",
    "\n",
    "There are other transforms which are associated with different encoders. To see why we might be interested in these other transforms, observe that under the JWT, $\\tilde{a}_p$ acts not only on qubit $p$ but also on qubits $0, \\ldots, p-1$. This means that fermionic operators with low weight can get mapped to qubit operators with high weight, where by weight we mean the number of modes or qubits an operators acts on. There are some disadvantages to having high-weight operators; for instance, they may require more gates to simulate and are more expensive to measure on some near-term hardware platforms. In the worst case, the annihilation operator on the last mode will map to an operator which acts on all the qubits. To emphasize this point let's apply the JWT to the annihilation operator on mode 99:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "4ada8be6a86c"
   },
   "outputs": [],
   "source": [
    "print(jordan_wigner(FermionOperator('99')))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "aa41f1a05739"
   },
   "source": [
    "The purpose of the string of Pauli $Z$'s is to introduce the phase factor $(-1)^{\\sum_{q=0}^{p-1} n_q}$ when acting on a computational basis state; when $e$ is the identity encoder, the modulo-2 sum $\\sum_{q=0}^{p-1} n_q$ is computed as $\\sum_{q=0}^{p-1} z_q$, which requires reading $p$ bits and leads to a Pauli $Z$ string with weight $p$. A simple solution to this problem is to consider instead the encoder defined by\n",
    "$$e(x)_p = \\sum_{q=0}^p x_q \\quad (\\text{mod 2}),$$\n",
    "which is associated with the mapping of basis vectors\n",
    "$\\lvert n_0, \\ldots, n_{N-1} \\rangle \\mapsto \\lvert z_0, \\ldots, z_{N-1} \\rangle,$\n",
    "where $z_p = \\sum_{q=0}^p n_q$ (again addition is modulo 2). With this encoding, we can compute the sum $\\sum_{q=0}^{p-1} n_q$ by reading just one bit because this is the value stored by $z_{p-1}$. The associated transform is called the parity transform because the $p$-th qubit is storing the parity (modulo-2 sum) of modes $0, \\ldots, p$. Under the parity transform, annihilation operators are mapped as follows:\n",
    "$$\\begin{aligned}\n",
    "    a_p &\\mapsto \\frac{1}{2} (X_p Z_{p - 1} + \\mathrm{i}Y_p) X_{p + 1} \\cdots X_{N} \\\\\n",
    "    &= \\frac{1}{4} [(X_p + \\mathrm{i} Y_p) (I + Z_{p - 1}) -\n",
    "                (X_p - \\mathrm{i} Y_p) (I - Z_{p - 1})]\n",
    "               X_{p + 1} \\cdots X_{N} \\\\\n",
    "    &= [(\\lvert{0}\\rangle\\langle{1}\\rvert)_p (\\lvert{0}\\rangle\\langle{0}\\rvert)_{p - 1} -\n",
    "        (\\lvert{0}\\rangle\\langle{1}\\rvert)_p (\\lvert{1}\\rangle\\langle{1}\\rvert)_{p - 1}]\n",
    "       X_{p + 1} \\cdots X_{N} \\\\\n",
    "\\end{aligned}$$\n",
    "\n",
    "The term in brackets in the last line means \"if $z_p = n_p$ then annihilate in mode $p$; otherwise, create in mode $p$ and attach a minus sign\". The value stored by $z_{p-1}$ contains the information needed to determine whether a minus sign should be attached or not. However, now there is a string of Pauli $X$'s acting on modes $p+1, \\ldots, N-1$ and hence using the parity transform also yields operators with high weight. These Pauli $X$'s perform the necessary update to $z_{p+1}, \\ldots, z_{N-1}$ which is needed if the value of $n_{p}$ changes. In the worst case, the annihilation operator on the first mode will map to an operator which acts on all the qubits.\n",
    "\n",
    "Since the parity transform does not offer any advantages over the JWT, OpenFermion does not include a standalone function to perform it. However, there is functionality for defining new transforms by specifying an encoder and decoder pair, also known as a binary code (in our examples the decoder is simply the inverse mapping), and the binary code which defines the parity transform is included in the library as an example. See [Lowering qubit requirements using binary codes](./binary_code_transforms_demo.ipynb) for a demonstration of this functionality and how it can be used to reduce the qubit resources required for certain applications.\n",
    "\n",
    "Let's use this functionality to map our previously instantiated FermionOperators to QubitOperators using the parity transform with 10 total modes and check that the resulting operators satisfy the expected relations."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "a38415103b77"
   },
   "outputs": [],
   "source": [
    "# Set the number of modes in the system\n",
    "n_modes = 10\n",
    "\n",
    "# Define a function to perform the parity transform\n",
    "def parity(fermion_operator, n_modes):\n",
    "    return binary_code_transform(fermion_operator, parity_code(n_modes))\n",
    "\n",
    "# Map FermionOperators to QubitOperators using the parity transform\n",
    "annihilate_2_parity = parity(annihilate_2, n_modes)\n",
    "create_2_parity = parity(create_2, n_modes)\n",
    "annihilate_5_parity = parity(annihilate_5, n_modes)\n",
    "create_5_parity = parity(create_5, n_modes)\n",
    "num_2_parity = parity(num_2, n_modes)\n",
    "num_5_parity = parity(num_5, n_modes)\n",
    "\n",
    "# Check the canonical anticommutation relations\n",
    "assert anticommutator(annihilate_5_parity, annihilate_2_parity) == zero\n",
    "assert anticommutator(annihilate_5_parity, annihilate_5_parity) == zero\n",
    "assert anticommutator(annihilate_5_parity, create_2_parity) == zero\n",
    "assert anticommutator(annihilate_5_parity, create_5_parity) == identity\n",
    "\n",
    "# Check that the occupation number operators commute\n",
    "assert commutator(num_2_parity, num_5_parity) == zero\n",
    "\n",
    "# Print some output\n",
    "print(\"annihilate_2_parity = \\n{}\".format(annihilate_2_parity))\n",
    "print('')\n",
    "print(\"create_2_parity = \\n{}\".format(create_2_parity))\n",
    "print('')\n",
    "print(\"annihilate_5_parity = \\n{}\".format(annihilate_5_parity))\n",
    "print('')\n",
    "print(\"create_5_parity = \\n{}\".format(create_5_parity))\n",
    "print('')\n",
    "print(\"num_2_parity = \\n{}\".format(num_2_parity))\n",
    "print('')\n",
    "print(\"num_5_parity = \\n{}\".format(num_5_parity))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "a1025a11bdbd"
   },
   "source": [
    "Now let's map one of the FermionOperators again but with the total number of modes set to 100."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "5e702e7340a1"
   },
   "outputs": [],
   "source": [
    "print(parity(annihilate_2, 100))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "d82be6e57032"
   },
   "source": [
    "Note that with the JWT, it is not necessary to specify the total number of modes in the system because $\\tilde{a}_p$ only acts on qubits $0, \\ldots, p$ and not any higher ones."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "c6e4bc777e97"
   },
   "source": [
    "### The Bravyi-Kitaev transform\n",
    "\n",
    "The discussion above suggests that we can think of the action of a transformed annihilation operator $\\tilde{a}_p$ on a computational basis vector $\\lvert z \\rangle$ as a 4-step classical algorithm:\n",
    "1. Check if $n_p = 0$. If so, then output the zero vector. Otherwise,\n",
    "2. Update the bit stored by $z_p$.\n",
    "3. Update the rest of the bits $z_q$, $q \\neq p$.\n",
    "4. Multiply by the parity $\\sum_{q=0}^{p-1} n_p$.\n",
    "\n",
    "Under the JWT, Steps 1, 2, and 3 are represented by the operator $(\\lvert{0}\\rangle\\langle{1}\\rvert)_p$ and Step 4 is accomplished by the operator $Z_{0} \\cdots Z_{p-1}$ (Step 3 actually requires no action).\n",
    "Under the parity transform, Steps 1, 2, and 4 are represented by the operator\n",
    "$(\\lvert{0}\\rangle\\langle{1}\\rvert)_p (\\lvert{0}\\rangle\\langle{0}\\rvert)_{p - 1} -\n",
    "(\\lvert{0}\\rangle\\langle{1}\\rvert)_p (\\lvert{1}\\rangle\\langle{1}\\rvert)_{p - 1}$ and Step 3 is accomplished by the operator $X_{p+1} \\cdots X_{N-1}$.\n",
    "\n",
    "To obtain a simpler description of these and other transforms (with an aim at generalizing), it is better to put aside the ladder operators and work with an alternative set of $2N$ operators defined by\n",
    "$$c_p = a_p + a_p^\\dagger\\,, \\qquad d_p = -\\mathrm{i} (a_p - a_p^\\dagger)\\,.$$\n",
    "These operators are known as Majorana operators. Note that if we describe how Majorana operators should be transformed, then we also know how the annihilation operators should be transformed, since\n",
    "$$a_p = \\frac{1}{2} (c_p + \\mathrm{i} d_p).$$\n",
    "\n",
    "For simplicity, let's consider just the $c_p$; the $d_p$ are treated similarly. The action of $c_p$ on a fermionic basis vector is given by\n",
    "$$c_p \\lvert n_0, \\ldots, n_{p-1}, n_p, n_{p+1}, \\ldots, n_{N-1} \\rangle =\n",
    "(-1)^{\\sum_{q=0}^{p-1} n_q} \\lvert n_0, \\ldots, n_{p-1}, 1 - n_p, n_{p+1}, \\ldots, n_{N-1} \\rangle$$\n",
    "\n",
    "In words, $c_p$ flips the occupation of mode $p$ and multiplies by the ever-present parity factor. If we transform $c_p$ to a qubit operator $\\tilde{c}_p$, we should be able to describe the action of $\\tilde{c}_p$ on a computational basis vector $\\lvert z \\rangle$ with a 2-step classical algorithm:\n",
    "1. Update the string $z$ to a new string $z'$.\n",
    "2. Multiply by the parity $\\sum_{q=0}^{p-1} n_q$.\n",
    "\n",
    "Step 1 amounts to flipping some bits, so it will be performed by some Pauli $X$'s, and Step 2 will be performed by some Pauli $Z$'s. So $\\tilde{c}_p$ should take the form\n",
    "$$\\tilde{c}_p = X_{U(p)} Z_{P(p - 1)},$$\n",
    "where $U(j)$ is the set of bits that need to be updated upon flipping $n_j$, and $P(j)$ is a set of bits that stores the sum $\\sum_{q=0}^{j} n_q$ (let's define $P(-1)$ to be the empty set). Let's see how this looks under the JWT and parity transforms."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "015dfec251d6"
   },
   "outputs": [],
   "source": [
    "# Create a Majorana operator from our existing operators\n",
    "c_5 = annihilate_5 + create_5\n",
    "\n",
    "# Set the number of modes (required for the parity transform)\n",
    "n_modes = 10\n",
    "\n",
    "# Transform the Majorana operator to a QubitOperator in two different ways\n",
    "c_5_jw = jordan_wigner(c_5)\n",
    "c_5_parity = parity(c_5, n_modes)\n",
    "\n",
    "# Print some output\n",
    "print(\"c_5_jw = \\n{}\".format(c_5_jw))\n",
    "print('')\n",
    "print(\"c_5_parity = \\n{}\".format(c_5_parity))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "46d3051b2d8a"
   },
   "source": [
    "For the JWT, $U(j) = \\{j\\}$ and $P(j) = \\{0, \\ldots, j\\}$, whereas for the parity transform, $U(j) = \\{j, \\ldots, N-1\\}$ and $P(j) = \\{j\\}$. The size of these sets can be as large as $N$, the total number of modes. These sets are determined by the encoding function $e$.\n",
    "\n",
    "It is possible to pick a clever encoder with the property that these sets have size $O(\\log N)$. The corresponding transform will map annihilation operators to qubit operators with weight $O(\\log N)$, which is much smaller than the $\\Omega(N)$ weight associated with the JWT and parity transforms. This fact was noticed by [Bravyi and Kitaev](https://arxiv.org/abs/quant-ph/0003137), and later [Havlíček and others](https://arxiv.org/abs/1701.07072) pointed out that the encoder which achieves this is implemented by a classical data structure called a Fenwick tree. The transforms described in these two papers actually correspond to different variants of the Fenwick tree data structure and give different results when the total number of modes is not a power of 2. OpenFermion implements the one from the first paper as `bravyi_kitaev` and the one from the second paper as `bravyi_kitaev_tree`. Generally, the first one (`bravyi_kitaev`) is preferred because it results in operators with lower weight and is faster to compute.\n",
    "\n",
    "Let's transform our previously instantiated Majorana operator using the Bravyi-Kitaev transform."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "194346f319ab"
   },
   "outputs": [],
   "source": [
    "c_5_bk = bravyi_kitaev(c_5, n_modes)\n",
    "print(\"c_5_bk = \\n{}\".format(c_5_bk))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "2769a6ac2001"
   },
   "source": [
    "The advantage of the Bravyi-Kitaev transform is not apparent in a system with so few modes. Let's look at a system with 100 modes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "396cba1df4f1"
   },
   "outputs": [],
   "source": [
    "n_modes = 100\n",
    "\n",
    "# Initialize some Majorana operators\n",
    "c_17 = FermionOperator('[17] + [17^]')\n",
    "c_50 = FermionOperator('[50] + [50^]')\n",
    "c_73 = FermionOperator('[73] + [73^]')\n",
    "\n",
    "# Map to QubitOperators\n",
    "c_17_jw = jordan_wigner(c_17)\n",
    "c_50_jw = jordan_wigner(c_50)\n",
    "c_73_jw = jordan_wigner(c_73)\n",
    "c_17_parity = parity(c_17, n_modes)\n",
    "c_50_parity = parity(c_50, n_modes)\n",
    "c_73_parity = parity(c_73, n_modes)\n",
    "c_17_bk = bravyi_kitaev(c_17, n_modes)\n",
    "c_50_bk = bravyi_kitaev(c_50, n_modes)\n",
    "c_73_bk = bravyi_kitaev(c_73, n_modes)\n",
    "\n",
    "# Print some output\n",
    "print(\"Jordan-Wigner\\n\"\n",
    "      \"-------------\")\n",
    "print(\"c_17_jw = \\n{}\".format(c_17_jw))\n",
    "print('')\n",
    "print(\"c_50_jw = \\n{}\".format(c_50_jw))\n",
    "print('')\n",
    "print(\"c_73_jw = \\n{}\".format(c_73_jw))\n",
    "print('')\n",
    "print(\"Parity\\n\"\n",
    "      \"------\")\n",
    "print(\"c_17_parity = \\n{}\".format(c_17_parity))\n",
    "print('')\n",
    "print(\"c_50_parity = \\n{}\".format(c_50_parity))\n",
    "print('')\n",
    "print(\"c_73_parity = \\n{}\".format(c_73_parity))\n",
    "print('')\n",
    "print(\"Bravyi-Kitaev\\n\"\n",
    "      \"-------------\")\n",
    "print(\"c_17_bk = \\n{}\".format(c_17_bk))\n",
    "print('')\n",
    "print(\"c_50_bk = \\n{}\".format(c_50_bk))\n",
    "print('')\n",
    "print(\"c_73_bk = \\n{}\".format(c_73_bk))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "7391ad24f3f0"
   },
   "source": [
    "Now let's go back to a system with 10 modes and check that the Bravyi-Kitaev transformed operators satisfy the expected relations."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "1e95c8c945bd"
   },
   "outputs": [],
   "source": [
    "# Set the number of modes in the system\n",
    "n_modes = 10\n",
    "\n",
    "# Map FermionOperators to QubitOperators using the Bravyi-Kitaev transform\n",
    "annihilate_2_bk = bravyi_kitaev(annihilate_2, n_modes)\n",
    "create_2_bk = bravyi_kitaev(create_2, n_modes)\n",
    "annihilate_5_bk = bravyi_kitaev(annihilate_5, n_modes)\n",
    "create_5_bk = bravyi_kitaev(create_5, n_modes)\n",
    "num_2_bk = bravyi_kitaev(num_2, n_modes)\n",
    "num_5_bk = bravyi_kitaev(num_5, n_modes)\n",
    "\n",
    "# Check the canonical anticommutation relations\n",
    "assert anticommutator(annihilate_5_bk, annihilate_2_bk) == zero\n",
    "assert anticommutator(annihilate_5_bk, annihilate_5_bk) == zero\n",
    "assert anticommutator(annihilate_5_bk, create_2_bk) == zero\n",
    "assert anticommutator(annihilate_5_bk, create_5_bk) == identity\n",
    "\n",
    "# Check that the occupation number operators commute\n",
    "assert commutator(num_2_bk, num_5_bk) == zero\n",
    "\n",
    "# Print some output\n",
    "print(\"annihilate_2_bk = \\n{}\".format(annihilate_2_bk))\n",
    "print('')\n",
    "print(\"create_2_bk = \\n{}\".format(create_2_bk))\n",
    "print('')\n",
    "print(\"annihilate_5_bk = \\n{}\".format(annihilate_5_bk))\n",
    "print('')\n",
    "print(\"create_5_bk = \\n{}\".format(create_5_bk))\n",
    "print('')\n",
    "print(\"num_2_bk = \\n{}\".format(num_2_bk))\n",
    "print('')\n",
    "print(\"num_5_bk = \\n{}\".format(num_5_bk))"
   ]
  }
 ],
 "metadata": {
  "colab": {
   "name": "jordan_wigner_and_bravyi_kitaev_transforms.ipynb",
   "toc_visible": true
  },
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
